package com.carlo.service.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.BodyPart;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeUtility;
import javax.mail.search.FlagTerm;

import org.apache.commons.lang3.StringUtils;

import com.carlo.bean.MailCfg;
import com.carlo.bean.MailFilter;
import com.carlo.bean.MailInfo;
import com.carlo.service.AbstractMail;
import com.carlo.service.IReceive;
import com.carlo.util.GlobalUtil;

public class ImapPop3ReceiveImpl extends AbstractMail implements IReceive {
	
	public ImapPop3ReceiveImpl(String temp){
		this.temp = temp;
	}

	public List<MailInfo> receive(MailCfg mailCfg, MailFilter mailFilter)
			throws Exception {
		Store store = null;
		Session session = null;
		Folder folder = null;
		List<MailInfo> resultList = new ArrayList<MailInfo>();
		try {
			Properties props = System.getProperties();
			if("imap".equals(mailCfg.getProtocol_type().toLowerCase())){
				props.setProperty("mail.store.protocol", "imap");
				props.setProperty("mail.imap.host", mailCfg.getServer_name());
				session = Session.getDefaultInstance(props, null);
				// 如果使用pop3协议这里改成pop3，如果使用imap则为imap，如果使用ssl连接这里应使用imaps
				store = session.getStore("imap");
				store.connect(mailCfg.getAccount(), mailCfg.getPwd());
				folder = store.getFolder("Inbox"); // 取得收件箱对象
				// 如果需要在取得邮件后将邮件置为已读则这里需要使用READ_WRITE,否则READ_ONLY就可以
				folder.open(Folder.READ_WRITE);
				resultList = imapRecv(folder, mailFilter);
			}else{
				props.setProperty("mail.store.protocol", "pop3");
				props.setProperty("mail.pop3.host", mailCfg.getServer_name());
				session = Session.getDefaultInstance(props, null);
				// 如果使用pop3协议这里改成pop3，如果使用imap则为imap，如果使用ssl连接这里应使用imaps
				store = session.getStore("pop3");
				store.connect(mailCfg.getAccount(), mailCfg.getPwd());
				folder = store.getFolder("Inbox"); // 取得收件箱对象
				// 如果需要在取得邮件后将邮件置为已读则这里需要使用READ_WRITE,否则READ_ONLY就可以
				folder.open(Folder.READ_WRITE);
				
				resultList = pop3Recv(folder, mailFilter);
			}

			return resultList;
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (folder != null) {
					//pop3如果要删除邮件，则需要设置 folder.close(true);，不然无法删除邮件
					folder.close(true);
					folder = null;
				}
				if (store != null) {
					store.close();
					store = null;
				}
			} catch (MessagingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}
	

	/**
	 * 
	* @Title: pop3Recv 
	* @Description: 使用pop3协议接收邮件
	* 		如何获取未读邮件：pop3不支持 通过 ((Message)mimeMessage).getFlags() 来获取邮件状态，imap支持。
	* 		
	* 		pop3无法直接获取获取未读邮件。 pop3与imap的区别就在于 能否直接获取未读邮件、也不能直接更改邮件状态，其他操作都一样	。	
	* 
	* 		借鉴网友的方法，可自己通过获取邮件的唯一辨识uid，记录解析过的邮件为已读邮件。
	* 		（即把解析过的邮件UID存储起来，作为判断邮件是否已读的依据）	
	* 
	* 		这里暂时没去实现，预留以后看看能否有更完美的方法（类似imap的可直接获取未读邮件）。
	* 
	* 		待完善......
	* 
	* 
	* //		FlagTerm ft = new FlagTerm(new Flags(Flags.Flag.SEEN), false); // false代表未读，true代表已读【imap支持获取未读邮件，pop3不支持】
				Message messages[] = folder.getMessages();  //获取所有邮件
	* @param @param inbox
	* @param @param mailFilter
	* @param @return
	* @param @throws Exception    设定文件 
	* @return List<MailInfo>    返回类型 
	* @author carlo
	* @throws
	 */
	public List<MailInfo> pop3Recv(Folder folder ,MailFilter mailFilter) throws Exception{
		List<MailInfo> resultList = new ArrayList<MailInfo>();
		List<Message> mesList = new ArrayList<Message>();
//		
////		FlagTerm ft = new FlagTerm(new Flags(Flags.Flag.SEEN), false); // false代表未读，true代表已读【imap支持获取未读邮件，pop3不支持】
////		Message messages[] = folder.search(ft); // 根据设置好的条件获取message
//		
		Message messages[] = folder.getMessages();  //pop3只能获取所有邮件
		
		for (int i = messages.length - 1; i >= 0; i--) {   
			//Message表示一个邮件
			Message msg = messages[i];
			//获取邮件标题
			String subject = msg.getSubject();
			StringBuffer bodyBuffer = new StringBuffer();
			getMailContent(msg , bodyBuffer);
			String body = GlobalUtil.clearHtmlLabel(bodyBuffer.toString());
			if(!isSatisfy(subject ,body , mailFilter)){  //如果不满足过滤条件，则跳过
            	continue;  
            }
			
			//如果标题和正文都满足，则再判断附件是否符合
			List<File> attachments = new ArrayList<File>();
			boolean attrIsSatisfy = readAttachment(msg , mailFilter , attachments);
			if(!attrIsSatisfy){
				continue;
			}
			
//			//读取邮件内容后，会自动把邮件设置为已读状态，所以这里需要重新设置为未读状态，后面才对满足条件的邮件进行手动设置为已读状态
//			folder.setFlags(new Message[]{ msg }, new Flags(Flags.Flag.SEEN),false);
//			
			mesList.add(msg);
			
			MailInfo mail = new MailInfo();
            mail.setSubject(subject);
            mail.setBody(body);
            mail.setAttachments(attachments);
            resultList.add(mail);
            break;
		}
		 //将刚才获取的邮件设置为已读
		 if (mesList.size() > 0) {
//////			 //设置为已读 (pop3不能更新邮件状态)
////			 Message[] savedMailMessage = new Message[mesList.size()];
////			 mesList.toArray(savedMailMessage);
////			 folder.setFlags(savedMailMessage, new Flags(Flags.Flag.DELETED),true);
//			 
			 //删除邮件 == pop3只能进行邮件删除
			 for(Message msg : mesList){
				 msg.setFlag(Flags.Flag.DELETED, true);    
			 }
		 }
		 return resultList;
		 
	}
	
	
	/**
	 * 
	* @Title: imapRecv 
	* @Description: 使用imap协议接收邮件  
	* @param @param inbox
	* @param @param mailFilter
	* @param @return
	* @param @throws Exception    设定文件 
	* @return List<MailInfo>    返回类型 
	* @author carlo
	* @throws
	 */
	public List<MailInfo> imapRecv(Folder folder ,MailFilter mailFilter) throws Exception{
		List<MailInfo> resultList = new ArrayList<MailInfo>();
		List<Message> mesList = new ArrayList<Message>();
		FlagTerm ft = new FlagTerm(new Flags(Flags.Flag.SEEN), false); // false代表未读，true代表已读【imap支持获取未读邮件，pop3不支持】
		Message messages[] = folder.search(ft); // 根据设置好的条件获取message
		for (int i = messages.length - 1; i >= 0; i--) {   
			//Message表示一个邮件
			Message msg = messages[i];
			//获取邮件标题
			String subject = msg.getSubject();
			StringBuffer bodyBuffer = new StringBuffer();
			getMailContent(msg , bodyBuffer);
			String body = GlobalUtil.clearHtmlLabel(bodyBuffer.toString());
			
			if(!isSatisfy(subject ,body , mailFilter)){  //如果不满足过滤条件，则跳过
            	continue;  
            }
			
			//如果标题和正文都满足，则再判断附件是否符合
			List<File> attachments = new ArrayList<File>();
			boolean attrIsSatisfy = readAttachment(msg , mailFilter , attachments);
			if(!attrIsSatisfy){
				continue;
			}
			
			//读取邮件内容后，会自动把邮件设置为已读状态，所以这里需要重新设置为未读状态，后面才对满足条件的邮件进行手动设置为已读状态
			folder.setFlags(new Message[]{ msg }, new Flags(Flags.Flag.SEEN),false);
			
			
			mesList.add(msg);
			
			MailInfo mail = new MailInfo();
            mail.setSubject(subject);
            mail.setBody(body);
            mail.setAttachments(attachments);
            resultList.add(mail);
		}
		 //将刚才获取的邮件设置为已读
		 if (mesList.size() > 0) {
			 //设置为已读
			 Message[] savedMailMessage = new Message[mesList.size()];
			 mesList.toArray(savedMailMessage);
			 folder.setFlags(savedMailMessage, new Flags(Flags.Flag.SEEN),true);
			 
//			 //移动到已删除
//			 for(Message msg : mesList){
//				 msg.setFlag(Flags.Flag.DELETED, true);
//			 }
		 }
		 return resultList;
	}
	
	/**
	 * 
	* @Title: isSatisfy 
	* @Description: 邮件过滤 ,满足条件则返回true，否则返回false
	* @param @param subject
	* @param @param body
	* @param @param attachs
	* @param @param mailFilter
	* @param @return    设定文件 
	* @return boolean    返回类型 
	* @author carlo
	* @throws
	 */
	public boolean isSatisfy(String subject , String  body ,MailFilter mailFilter){
		String subjectKeyWord = mailFilter.getSubjectKeyWord();
		String bodyKeyWord = mailFilter.getBodyKeyWord();

		boolean resultBoo = true;
		if(StringUtils.isNotBlank(subjectKeyWord)){
			if(mailFilter.isSubjectAdv()){  //正则匹配
				Pattern p = Pattern.compile(subjectKeyWord);
				Matcher m = p.matcher(subject);
				resultBoo = m.matches();
			}else{ //contain,字符串包含
				resultBoo = subject.contains(subjectKeyWord);
			}
		}
		if(!resultBoo){
			return false;
		}
		
		if(StringUtils.isNotBlank(bodyKeyWord)){
			if(mailFilter.isBodyAdv()){  //正则匹配
				Pattern p = Pattern.compile(bodyKeyWord);
				Matcher m = p.matcher(body);
				resultBoo = m.matches();
			}else{ //contain,字符串包含
				resultBoo = body.contains(bodyKeyWord);
			}
		}
		
		return resultBoo;
	}
	
	/**
	 * @throws Exception 
	 * 
	* @Title: getMailContent 
	* @Description: 获取邮件内容,获取html格式内容
	* @param @return    设定文件 
	* @return String    返回类型 
	* @author carlo
	* @throws
	 */
	public void getMailContent(Part part , StringBuffer bodyBuffer) throws Exception{
		//邮件类型为"text/plain"或者"text/html"，表示邮件内容为纯文本，
		//此时调用Message对象的getContent方法得到邮件内容，然后将返回对象的类型转换成String输出即可
//		System.out.println("getContentType = "+part.getContentType());
		if (part.isMimeType("text/html")) {
			bodyBuffer.append((String) part.getContent());
		} 
//		if (part.isMimeType("text/plain") || part.isMimeType("text/html")) {
//			bodyBuffer.append((String) part.getContent());
//		} 
		else if (part.isMimeType("multipart/*")) {
			Multipart multipart = (Multipart) part.getContent();
			int counts = multipart.getCount();
			for (int i = 0; i < counts; i++) {
				getMailContent(multipart.getBodyPart(i), bodyBuffer);
			}
		} else if (part.isMimeType("message/rfc822")) {
			getMailContent((Part) part.getContent(), bodyBuffer);
		} 
	}

	/**
	 * 
	* @Title: readAttachment 
	* @Description: 获取符合条件的附件,把邮件中的附件解析保存到临时目录temp下，返回附件文件的路径List集合
	* 	读取出来的附件信息如下：
	* 		CONTENTTYPE: IMAGE/PNG; name="=?UTF-8?B?546v5aKD5Y+Y6YeP6YWN572uLnBuZw==?="
			CONTENTTYPE: IMAGE/PNG; name="=?gbk?B?cGljdHVyZV8xLnBuZw==?="
		所以需要先判断附件名称的编码格式，然后再使用 MimeUtility.decodeText 解析出正确的内容。
		
	参考：https://www.cnblogs.com/huangminwen/archive/2016/11/27/6107078.html
	* @param @param part
	* @param @throws Exception    设定文件 
	* @return void    返回类型 
	* @author carlo
	* @throws
	 */
	private boolean readAttachment(Part part , MailFilter mailFilter , List<File> fileList) throws Exception {
		String attachmentKeyWord = mailFilter.getAttachmentKeyWord();
		String attachmentFileType = mailFilter.getAttachmentFileType();
		try {
			//“text/plain”或”text/html”为纯文本，如果为”multipart/*”表示邮件内容是一个复合类型
			if (part.isMimeType("multipart/*")) {
				//Part.getContent()方法返回一个Multipart对象，Multipart是一个容器，它装载多个body Part（正文、附件或内嵌资源）。
				Multipart mp = (Multipart) part.getContent();
				//一个Multipart对象包含一个或多个BodyPart对象，来组成邮件的正文部分（包括附件）。
				for (int i = 0; i < mp.getCount(); i++) {
					BodyPart mpart = mp.getBodyPart(i);
					String fileName = "";
					//BodyPart的getDisposition()方法获得邮件体的部署类型 ， 如果是附件，则返回的是Part.ATTACHMENT 或者 Part.INLINE
					String disposition = mpart.getDisposition();
					if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT)) || (disposition.equals(Part.INLINE)))) {
						fileName = mpart.getFileName();
						//判断附件名称的编码，然后使用 MimeUtility.decodeText 进行解析
						if (fileName.toLowerCase().contains("gb2312") 
								|| fileName.toLowerCase().contains("gbk")
								|| fileName.toLowerCase().contains("utf-8")){
							fileName = MimeUtility.decodeText(fileName);
						}
						boolean isSatisfy = true; //默认是满足条件
						if(StringUtils.isNotBlank(attachmentKeyWord)
								|| StringUtils.isNotBlank(attachmentFileType)){
							int index = fileName.lastIndexOf(".");
			    			String attachName = fileName.substring(0, index); //去除后缀的名称描述
			    			//附件名
			    			if(StringUtils.isNotBlank(attachmentKeyWord)){
				    			if(mailFilter.isAttachmAdv()){  //正则匹配
				    				Pattern p = Pattern.compile(attachmentKeyWord);
				    				Matcher m = p.matcher(attachName);
				    				isSatisfy = m.matches();
				    			}else{ //contain,字符串包含
				    				isSatisfy = attachName.contains(attachmentKeyWord);
				    			}
			    			}
			    			if(isSatisfy){  //附件名称符合过滤条件，再判断附件名类型是否符合过滤条件
			    				if(StringUtils.isNotBlank(attachmentFileType)){
			    					isSatisfy = fileName.endsWith(attachmentFileType);
			    				}
			    			}
						}
						//如果是满足条件，则把附件解析到临时目录fileTempPath下
						if(isSatisfy){
							String attachmentTempFile = GlobalUtil.writeInputStreamToFile(this.temp + fileName, mpart.getInputStream());
							fileList.add(new File(attachmentTempFile));
		    			}
					}
				}
				if(StringUtils.isNotEmpty(attachmentKeyWord) || StringUtils.isNotEmpty(attachmentFileType)){
					return !fileList.isEmpty();
				}else{
					return true;
				}
			}else{
				//如果为纯文本的邮件，当配置了需要对附件进行过滤时，返回false
				if(StringUtils.isNotEmpty(attachmentKeyWord) || StringUtils.isNotEmpty(attachmentFileType)){
					return false;
				}else{
					return true;
				}
			}
		} catch (Exception e) {
			//异常，则删除目录下的生成的文件
			if(!fileList.isEmpty()){
				for(File attachFile : fileList){
					try{
						if(attachFile.exists()){
							attachFile.delete();
						}
					}catch(Exception ee){}
				}
				fileList.clear();
			}
			throw new Exception("邮件读取附件失败！");
		} 
	}
}
